#pragma once

#include <cassert>
#include <memory>
#include <iostream>
#include <vector>
#include "managers.hpp"

//#define __DEBUG__
#ifdef __DEBUG__
#define __TAB__ do {\
	printf("class:%s, func:%s, line:%d;\n", typeid(*this).name(),  __func__, __LINE__);\
} while(0)
#else
#define __TAB__
#endif

using std::unique_ptr;
using std::string;
using std::endl;
using std::to_string;
using std::ostream;
using std::vector;
using std::cerr;

class BaseAST {
	public:
		virtual ~BaseAST() = default;
		virtual string *Dump(ostream &out) const = 0;
		virtual string *Dump(ostream &out, const IdentType &ident_type) const = 0;
		virtual string *Dump(ostream &out, const string *info) const = 0;
};

class DefaultNullAST: public BaseAST {
	public:

		string *Dump(ostream &out) const override {
			return nullptr;
		}

		string *Dump(ostream &out, const IdentType &ident_type) const override {
			return nullptr;
		}

		string *Dump(ostream &out, const string *info) const override {
			return nullptr;
		}
};

class CompUnitAST : public DefaultNullAST {
	public:
		unique_ptr<BaseAST> func_def;

		string *Dump(ostream &out) const override {
			__TAB__;
			return func_def->Dump(out);
		}
};

class FuncDefAST : public DefaultNullAST {
	public:
		unique_ptr<BaseAST> func_type;
		unique_ptr<string> ident;
		unique_ptr<BaseAST> block;

		string *Dump(ostream &out) const override {
			__TAB__;
			out << "fun";
			out << " @" << *ident << "():";
			func_type->Dump(out);
			out << " {" << endl;
			out << "%entry:" << endl;
			block->Dump(out, nullptr);
			out << "}" << endl;
			return nullptr;
		}

};

class FuncTypeAST: public DefaultNullAST {
	public:
		unique_ptr<string> type_name;

		string *Dump(ostream &out) const override {
			out << " " << *type_name;
			return nullptr;
		}
};

class BlockAST: public DefaultNullAST {
	public:
		unique_ptr<vector<unique_ptr<BaseAST>>> block_items;

		string *Dump(ostream &out, const string *info) const override {
			__TAB__;
			symtab_manager.Forward();
			bool flag = true;
			bool last_is_ret = false;
			for (auto iter = block_items->begin(); iter != block_items->end(); ++iter) {
				auto res = unique_ptr<string>((*iter)->Dump(out));
				if (res != nullptr && (*res).substr(0, 3).compare("ret") == 0) {
					if (iter == block_items->end() - 1) {
						last_is_ret = true;
					}
					if (!flag) {
						auto new_entry = unique_ptr<string>(label_manager.PopTemp("phase"));
						out << *new_entry << ":" << endl;
					} else flag = false;
					out << "  " << *res << endl;
				}
			}
			symtab_manager.Backward();
			if (info != nullptr && !last_is_ret) out << "  jump " << *info << endl;
			return nullptr;
		}
};

class BlockItemAST: public DefaultNullAST {
	public:
		enum Type {
			TypeDecl = 0,
			TypeStmt
		} type;
		unique_ptr<BaseAST> sub_ast;

		string *Dump(ostream &out) const override {
			__TAB__;
			switch (type) {
				case TypeDecl: return sub_ast->Dump(out);
				case TypeStmt: return sub_ast->Dump(out, nullptr);
				default: assert(false);
			}
		}
};

class StmtAST: public DefaultNullAST {
	public:
		enum Type {
			TypeLVal = 0,
			TypeNone,
			TypeSingle,
			TypeBlock,
			TypeIf,
			TypeIfElse,
			TypeReturn,
			TypeReturnNone,
		} type;
		unique_ptr<string> lval;
		unique_ptr<BaseAST> first_ast; 
		unique_ptr<BaseAST> second_ast; 
		unique_ptr<BaseAST> third_ast; 

		string *Dump(ostream &out, const string *info) const override {
			__TAB__;
			switch (type) {
				case TypeLVal: {
					auto entry = symtab_manager.Get(*lval);
					auto rh = unique_ptr<string>(first_ast->Dump(out));
					out << "  store " << *rh << ", " << entry.name << endl;
					if (info != nullptr)
						out << "  jump " << *info << endl;
					return nullptr;
				}
				case TypeNone: {
					if (info != nullptr)
						out << "  jump " << *info << endl;
					return nullptr;
				}
				case TypeSingle: {
					auto res = first_ast->Dump(out);
					if (info != nullptr)
						out << "  jump " << *info << endl;
					return res;
				}
				case TypeBlock: {
					auto res = first_ast->Dump(out, info);
					return res;
				}
				case TypeIf: {
					auto then_label = label_manager.PopTemp("then");
					auto end_label = label_manager.PopTemp("end");

					auto bool_label = unique_ptr<string>(first_ast->Dump(out));
					out << "  br " << *bool_label << ", " << *then_label << ", " << *end_label << endl;
					out << *then_label << ":" << endl;
					auto res = unique_ptr<string>(second_ast->Dump(out, end_label));
					if (res != nullptr && (*res).substr(0, 3).compare("ret") == 0)
						out << "  " << *res << endl;
					out << *end_label << ":" <<endl;

					delete then_label;
					delete end_label;
					return nullptr;
				}
				case TypeIfElse: {
					auto then_label = label_manager.PopTemp("then");
					//auto end_label = label_manager.PopTemp("end");
					//auto else_label = label_manager.PopTemp("else");

					auto bool_label = unique_ptr<string>(first_ast->Dump(out));
					out << "  br " << *bool_label << ", " << *then_label;
					return nullptr;	
				}
				case TypeReturn: {
					auto rh = unique_ptr<string>(first_ast->Dump(out));
					return new string("ret "+*rh);
				}
				case TypeReturnNone: {
					return new string("ret");
				}
			}
		}
};

